package com.softserveinc.ita.kaiji.multiplay.domain.room;

import com.softserveinc.ita.kaiji.multiplay.domain.GameElementBase;
import com.softserveinc.ita.kaiji.multiplay.domain.InvalidActionException;
import com.softserveinc.ita.kaiji.multiplay.domain.config.BaseConfigForGame;
import com.softserveinc.ita.kaiji.multiplay.domain.message.InOutMessage;
import com.softserveinc.ita.kaiji.multiplay.domain.player.BasePlayer;
import com.softserveinc.ita.kaiji.multiplay.domain.player.PlayerOnlineInfo;
import com.softserveinc.ita.kaiji.multiplay.domain.player.StateOfPlayer;
import com.softserveinc.ita.kaiji.multiplay.dto.client.DtoClient;
import com.softserveinc.ita.kaiji.multiplay.dto.server.DtoError;
import com.softserveinc.ita.kaiji.multiplay.dto.server.room.DtoPlayerRoom;
import org.apache.log4j.Logger;

import javax.persistence.Entity;
import javax.persistence.Id;

/**
 * Project testFromEmpty
 * Created by mikeldpl
 * 21.03.2015 23:38.
 */
@Entity
public abstract class PlayRoom extends GameElementBase implements Room {
	private static final Logger LOG = Logger.getLogger(PlayRoom.class);
	@Id
	private final String name;
	private final Players players;
	private final TimerForGame timer;
	private StateOfRoom state = StateOfRoom.SELECTING;
	private volatile Thread thread;

	public PlayRoom(BaseConfigForGame configForGame) {
		this.name = configForGame.getName();
		this.players = new Players(configForGame.getMaxNumberOfPlayersConfig());
		this.timer = new TimerForGame(configForGame.getDurationConfig());
	}


	public synchronized boolean addPlayer(PlayerOnlineInfo info) {
		BasePlayer playerByName = players.getPlayerByName(info.getName());
		InOutMessage msg = new InOutMessage(playerByName, null);
		msg.addDomainThatMustBeSanded(this);
		boolean res = true;
		switch (state) {
			case GAME:
				if (res = (playerByName != null))
					playerByName.setState(msg, StateOfPlayer.ONLINE);
				break;
			case SELECTING:
				if (playerByName == null) {
					playerByName = getNewPlayer(info.getName());
					res = addPlayer(msg, playerByName);
				}
				break;
		}
		if (res) {
			if (getPlayers().isFull() && getState() == StateOfRoom.SELECTING) {
				start(msg);
			}
			addSubscriber(info);
			playerByName.addSubscriber(info);
			msg.addDomainThatMustBeSanded(playerByName);
			msg.sendResponse();
		} else {
			info.send(new DtoError().setErr("Room is entire"));
		}
		return res;
	}


	public synchronized boolean removePlayer(PlayerOnlineInfo info) {
		BasePlayer playerByName = players.getPlayerByName(info.getName());
		if (playerByName != null) {
			removeSubscriber(info);
			InOutMessage msg = new InOutMessage(playerByName, null);
			switch (state) {
				case GAME:
					playerByName.setState(msg, StateOfPlayer.OFFLINE);
					break;
				case SELECTING:
					removePlayer(msg, playerByName);
					break;
			}
			msg.sendResponse();
			return true;
		}
		return false;
	}

	//-------------------------------------------------------------------------------------setter with msg
	private boolean addPlayer(InOutMessage msg, BasePlayer newPlayer) {
		if (players.addPlayer(newPlayer)) {
			msg.addDomainThatMustBeSanded(this);
			return true;
		}
		return false;
	}

	private boolean removePlayer(InOutMessage msg, BasePlayer player) {
		if (players.removePlayer(player)) {
			msg.addDomainThatMustBeSanded(this);
			return true;
		}
		return false;
	}

	public void setTime(InOutMessage msg, long leftTime) {
		getTimer().setLeftTime(leftTime);
		msg.addDomainThatMustBeSanded(this);
		this.notifyAll();
	}
//--------------------------------------------------------------------------------------------------

	public synchronized void inComeMessage(PlayerOnlineInfo info, DtoClient msg) {
		BasePlayer playerByName = players.getPlayerByName(info.getName());
		if (playerByName != null && playerByName.getState() != StateOfPlayer.OFFLINE) {
			InOutMessage inOutMessage = new InOutMessage(playerByName, msg);
			processMessage(inOutMessage);
			inOutMessage.sendResponse();
		} else {
			throw new InvalidActionException(info.getName() + " not in game " + getName());
		}
	}

	public synchronized void start(InOutMessage msg) {
		if (thread == null) {
			setState(msg, StateOfRoom.GAME);
			timer.start();
			thread = new Thread(new Runnable() {
				@Override
				public void run() {
					synchronized (PlayRoom.this) {
						try {
							long leftInMomentOfCheck;
							while ((leftInMomentOfCheck = timer.left()) > 0 && !thread.isInterrupted()) {
								PlayRoom.this.wait(leftInMomentOfCheck);
							}
							if (LOG.isInfoEnabled()) {
								LOG.info(" Room " + getName() + " is finish the game!");
							}
						} catch (InterruptedException e) {
							if (LOG.isInfoEnabled()) {
								LOG.info(" Room " + getName() + " is interrupted!!");
							}
						}
						thread = null;
						stop();
					}
				}
			});
			thread.start();
		} else {
			throw new InvalidActionException(getName() + " can't be started, it is in the game.");
		}
	}

	public synchronized void stop() {
		System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + timer.left());
		if (thread != null) {
			thread.interrupt();
		} else if (getState() == StateOfRoom.GAME) {
			InOutMessage msg = new InOutMessage(null, null);
			clear(msg);
			msg.sendResponse();
			removeAllSubscribers();
		} else {
			throw new InvalidActionException(getName() + " can't be stopped, it is already stopped.");
		}
	}

	public void clear(InOutMessage msg) {
		setTime(msg, 0);
		Object[] objects = getPlayers().getPlayers().toArray();
		setState(msg, StateOfRoom.SELECTING);
		for (Object basePlayer : objects) {
			removePlayer(msg, (BasePlayer) basePlayer);
		}
	}


	//----------------------------------------------------------------
	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		return getClass().isInstance(o) &&
				getName().equals(((PlayRoom) o).getName());
	}


	@Override
	public int hashCode() {
		return getName().hashCode();
	}

	//----------------------------------------------------------------
	@Override
	public DtoPlayerRoom getDto() {
		return new DtoPlayerRoom(getName());
	}

	@Override
	public DtoPlayerRoom getEntireDto() {
		DtoPlayerRoom dtoPlayerRoom = getDto().setState(getState())
				.setTime(getState() == StateOfRoom.SELECTING ? timer.getDurationConfig() : timer.left());
		for (BasePlayer player : players.getPlayers()) {
			dtoPlayerRoom.addPlayer(player.getEntirePublicDto());
		}
		return dtoPlayerRoom;
	}

	//-----------------------------------------------------------------------getters and setters
	public StateOfRoom getState() {
		return state;
	}

	public void setState(InOutMessage msg, StateOfRoom state) {
		this.state = state;
		msg.addDomainThatMustBeSanded(this);
	}

	public String getName() {
		return name;
	}

	public Players getPlayers() {
		return players;
	}

	public TimerForGame getTimer() {
		return timer;
	}

	//------------------------------------------------------------------------------------factory method
	public abstract BasePlayer getNewPlayer(String name);

}
